package asset;

import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Define {
    
    private static Map<String, Integer> regToIndex = Stream.of(new Object[][] { 
        {"$zero",   0b00000},  // 0
        {"$at",     0b00001},  // 由编译器生成的复合指令使用
        {"$v0",     0b00010},  // 计算结果和表达式求值
        {"$v1",     0b00011},
        {"$a0",     0b00100},  // 参数
        {"$a1",     0b00101},
        {"$a2",     0b00110},
        {"$a3",     0b00111},
        {"$t0",     0b01000},  // 临时变量
        {"$t1",     0b01001},
        {"$t2",     0b01010},
        {"$t3",     0b01011},
        {"$t4",     0b01100},
        {"$t5",     0b01101},
        {"$t6",     0b01110},
        {"$t7",     0b01111},
        {"$s0",     0b10000},  // 保留寄存器
        {"$s1",     0b10001},
        {"$s2",     0b10010},
        {"$s3",     0b10011},
        {"$s4",     0b10100},
        {"$s5",     0b10101},
        {"$s6",     0b10110},
        {"$s7",     0b10111},  // 更多临时变量
        {"$t8",     0b11000},
        {"$t9",     0b11001},
        {"$k0",     0b11010},
        {"$k1",     0b11011},
        {"$gp",     0b11100},  // 全局指针
        {"$sp",     0b11101},  // 栈指针
        {"$fp",     0b11110},  // 帧指针
        {"$ra",     0b11111}  // 返回地址
    }).collect(Collectors.toMap(data -> (String) data[0], data -> (Integer) data[1]));

    private static Map<Integer,String> indexToReg = regToIndex.entrySet().stream().
        collect(Collectors.toMap(data -> data.getValue(), data -> data.getKey()));
    
    private static Map<String,String> rIndexToInst = Stream.of(new String[][]{
        {"100000",  "add"},
        {"100010",  "sub"},
        {"100100",  "and"},
        {"100101",  "or"},
        {"100111",  "nor"},
        {"101010",  "slt"},
        {"101011",  "sltu"},
        {"000000",  "sll"},
        {"000010",  "srl"},
        {"001000",  "jr"},
        {"011000",  "mult"},
        {"011001",  "multu"},
        {"011010",  "div"},
        {"011011",  "divu"},
        {"010000",  "mfhi"},
        {"010010",  "mflo"},
        {"010001",  "mthi"},
        {"010011",  "mtlo"}
    }).collect(Collectors.toMap(data -> data[0], data -> data[1]));

    private static Map<String,String> rInstToIndex = rIndexToInst.entrySet().stream().
        collect(Collectors.toMap(data -> data.getValue(), data -> data.getKey()));

    private static Map<String, Integer> rFormat = Stream.of(new Object[][] { 
            {"add",     0b111},
            {"sub",     0b111},
            {"and",     0b111},
            {"or",      0b111},
            {"nor",     0b111},
            {"slt",     0b111},
            {"sltu",    0b111},
            {"sll",     0b011},
            {"srl",     0b011},
            {"jr",      0b100},
            {"nop",     0b000},
            {"mult",    0b110},
            {"multu",   0b110},
            {"div",     0b110},
            {"divu",    0b110},
            {"mfhi",    0b001},
            {"mflo",    0b001},
            {"mthi",    0b100},
            {"mtlo",    0b100}
        }).collect(Collectors.toMap(data -> (String) data[0], data -> (Integer) data[1]));
    
    private static Map<String,String> iIndexToInst = Stream.of(new String[][]{
        {"001000",   "addi" },
        {"100011",   "lw"   },
        {"101011",   "sw"   },
        {"000100",   "beq"  },
        {"000101",   "bne"  },
        {"001010",   "slti" },
        {"001011",   "sltiu"},
        {"001111",   "lui"  },
        {"001101",   "ori"  },
        {"001100",   "andi" },
        {"100001",   "lh"   },
        {"101001",   "sh"   },
        {"100000",   "lb"   },
        {"101000",   "sb"   }
    }).collect(Collectors.toMap(strs -> strs[0], strs -> strs[1]));

    private static Map<String,String> iInstToIndex = iIndexToInst.entrySet().stream().
        collect(Collectors.toMap(strs -> strs.getValue(), strs -> strs.getKey()));
    
    private static Map<String,String> iFormat = Stream.of(new String[][]{
        {"sltiu",   "cal"},
        {"ori",     "cal"},
        {"andi",    "cal"},
        {"slti",    "cal"},
        {"addi",    "cal"},
        {"lw",      "load"},
        {"sw",      "store"},
        {"lh",      "load"},
        {"sh",      "store"},
        {"lb",      "load"},
        {"sb",      "store"},
        {"beq",     "branch"},
        {"bne",     "branch"},
        {"lui",     "imm"}
    }).collect(Collectors.toMap(strs -> strs[0], strs -> strs[1]));

    private static Map<String,String> jIndexToInst = Stream.of(new String[][]{
        {"000010",   "j"  },
        {"000011",   "jal"}
    }).collect(Collectors.toMap(strs -> strs[0], strs -> strs[1]));

    private static Map<String,String> jInstToIndex = jIndexToInst.entrySet().stream().
        collect(Collectors.toMap(strs -> strs.getValue(), strs -> strs.getKey()));

    /**check Instruct Type
     * @param index : index or instruct string
     * @return byte:<p/>
     * 0 for R-type format or Special<p/>
     * 1 for I-type format<p/>
     * 2 for J-type format<p/>
     * @throws Exception if the index or instruct is not found
    */
    public static byte getInstType(String index) throws Exception {
        if (index.equals("000000") || rIndexToInst.containsValue(index)) {
            return 0;
        } else if (iIndexToInst.containsKey(index) || iIndexToInst.containsValue(index)) {
            return 1;
        } else if (jIndexToInst.containsKey(index) || jIndexToInst.containsValue(index)) {
            return 2;
        } else {
            throw new Exception("index" + index + "is not found");
        }
    }

    public static int getRformat(String funcString) throws Exception {
        if (rFormat.containsKey(funcString)) {
            return rFormat.get(funcString);
        }
        else {
            throw new Exception("wrong R_type_Ins : " + funcString);
        }
    }

    public static String getIformat(String funcString) throws Exception {
        if (iFormat.containsKey(funcString)) {
            return iFormat.get(funcString);
        }
        else {
            throw new Exception("wrong I_type_Ins : " + funcString);
        }
    }
    
    /**translate index to Instruct string
     * @param index : index string
     * @return {@code} InstructString
     * @throws Exception if the index is not found
     */
    public static String getInstString(String index,boolean isOpcode) throws Exception {
        if (!isOpcode) {
            return rIndexToInst.get(index);
        }
        else if (iIndexToInst.containsKey(index)) {
            return iIndexToInst.get(index);
        }
        else if (jIndexToInst.containsKey(index)) {
            return jIndexToInst.get(index);
        }
        return "nop";
    }

    public static String getInstString(String index) throws Exception {
        return getInstString(index,true);
    }

    /**
     * @param inst : instruct string
     * @return {@code} IndexString
     * @throws Exception if the instruct is not found
     */
    public static String getIndexString(String inst) throws Exception {
        switch (getInstType(inst)) {
            case 0:
                return rInstToIndex.get(inst);
            case 1:
                return iInstToIndex.get(inst);
            case 2:
                return jInstToIndex.get(inst);
            default:
                return "000000";
        }
    }

    /**get Register Index
     * 
     */
    public static int getRegIndex(String reg) throws Exception {
        String tempReg = reg.strip();
        if (tempReg.charAt(0) != '$') {
            throw new Exception(reg + "is illegal reg format");
        } 
        int a = -1;
        try {
            a = Integer.valueOf(tempReg.substring(1));
        } catch (Exception e) {
            a = -2;
        }
        if (0 <= a && a <= 31) {
            return a;
        } else if (a == -2 && regToIndex.containsKey(tempReg)) {
            return regToIndex.get(tempReg);         
        } else {
            throw new Exception(reg + "is illegal reg format");
        }
    }

    /**get Register name
     * 
     */
    public static String getRegName(int index) throws Exception {
        if (index >= 0 && index <= 31) {
            return indexToReg.get(index);
        } 
        else {
            throw new Exception(String.format("%d is over 0 to 31", index));
        }
    }

    //fixed parseInt
    public static int fixedParseInt(String numString,int radix) {
        String tempnumString = numString.strip();
        boolean sign = false;
        if (tempnumString.charAt(0) == '-') {
            sign = true;
            tempnumString = tempnumString.substring(1);
        }
        int strlen = tempnumString.length() - 1;
        int temp = strlen != 0 ? Integer.parseInt(tempnumString.substring(0,strlen),radix) : 0;
        temp = temp * radix + Integer.parseInt(tempnumString.substring(strlen),radix);
        return sign ? -temp : temp;
    }

    public static int fixedParseInt(String numString) {
        return fixedParseInt(numString,10);
    }

    //inner num stranslate tool
    /**get extended number <p/>
     * the second param length(binary length) will limit answer's max-value<p/>
     * identify hex,decimal,binary radix. 
     * Decimal takes precedence over binary when there is no prefix. 
     */
    public static int zeroExtNum(String numString,int length) {
        String innernum = numString.strip();
        int answer = 0;
        String prefix;
        if (innernum.charAt(0) == '-') {
            prefix = innernum.substring(1,3).toLowerCase();
            answer = 1;
        } 
        else {
            prefix = innernum.substring(0,2).toLowerCase();
        }

        if (prefix.equals("0x")) {
            answer = answer == 1 ? fixedParseInt("-" + innernum.substring(3),16)
                : fixedParseInt(innernum.substring(2),16);
            return answer & ((1 << length - 1) - 1 << 1) + 1;
        } 
        else if (prefix.equals("0b")) {
            answer = answer == 1 ? fixedParseInt("-" + innernum.substring(3),2)
                : fixedParseInt(innernum.substring(2),2); 
            return answer & ((1 << length - 1) - 1 << 1) + 1;
        }

        String digitRegex = "^[0-9]+$";
        if (innernum.matches(digitRegex)) {
            answer = fixedParseInt(innernum,10); 
            return answer & ((1 << length - 1) - 1 << 1) + 1;
        } 
        else {
            answer = fixedParseInt(innernum,16); 
            return answer & ((1 << length - 1) - 1 << 1) + 1;
        }
    }

    /**reload zeroExtNum(numstr,length) length=32 */
    public static int zeroExtNum(String numString) {
        return zeroExtNum(numString,32);
    }

    /**
     * get signed extend number<p/>
     */
    public static int sigExtNum(String numString,int length) {
        int tempnum = zeroExtNum(numString,length);
        boolean sign = (tempnum & (1 << length - 1)) != 0;
        if (sign) {
            tempnum |= ~(((1 << length - 1) - 1 << 1) + 1);
        }
        return tempnum;
    }

    /**reload sigExtNum(numstr, length) length = 32*/
    public static int sigExtNum(String numString) {
        return sigExtNum(numString,16);
    }

    public static int sigExtNum(int num,int length) {
        int tempnum = num & ((1 << length - 1) - 1 << 1) + 1;
        boolean sign = (tempnum & (1 << length - 1)) != 0;
        if (sign) {
            tempnum |= ~(((1 << length - 1) - 1 << 1) + 1);
        }
        return tempnum;
    }

    //private function: translate binary-length to decimal-length
    private static int binLenToDecLen(int binLength) {
        int temp = ((1 << binLength - 1) - 1 << 1) + 1;
        int decLength = 1;
        temp >>>= 1;
        temp /= 5;
        while (temp != 0) {
            temp /= 10;
            decLength++;
        }
        return decLength;
    } 

    /**get unsigned extended number string 
     * @param numString
     * @param oriLength : original binary length of numString
     * @param extLength : expected binary length of extString , Insufficient ones are filled with 0
     * {@param m    e , 0} for hex ; 1 for decimal ; 2 for binary
    */
    public static 
        String zeroExtStr(String numString,int oriLength,int extLength,byte mode) throws Exception {
        int tempnum = zeroExtNum(numString, oriLength)  & ((1 << extLength - 1) - 1 << 1) + 1;
        String answeString;
        int answerLength = 0;
        switch (mode) {
            case 0:
                answerLength = (extLength + 3) / 4;
                answeString =  String.format("%#" + answerLength + "x",tempnum);
                return answeString;
            case 1:
                answerLength = binLenToDecLen(extLength);
                answeString = String.format("%" + answerLength + "d", tempnum);
                return answeString;
            case 2:
                answeString = String.format("%" + extLength + "s", 
                    Integer.toBinaryString(tempnum));
                return answeString;
            default:
                throw new Exception(mode + "is wrong");
        }
    }

    /**reload zeroExrStr */
    public static String zeroExtStr(String numString,int oriLength,int extLength) throws Exception {
        return zeroExtStr(numString, oriLength, extLength,(byte)0);
    }

    /**get signed extended number string 
     * @param numString
     * @param oriLength : original binary length of numString
     * @param extLength : expected binary length of extString , Insufficient ones are filled with 0
     * {@param m    e , 0} for hex ; 1 for decimal ; 2 for binary
    */
    public static 
        String sigExtStr(String numString,int oriLength,int extLength,byte mode)throws Exception {
        int tempnum = sigExtNum(numString, oriLength);
        boolean sign = tempnum < 0;
        if (sign) {
            tempnum = -tempnum;
        }
        tempnum &= ((1 << extLength - 1) - 1 << 1) + 1;
        String answeString;
        int answerLength = 0;
        switch (mode) {
            case 0:
                answerLength = (extLength + 3) / 4;
                answeString =  String.format("%#" + answerLength + "x",tempnum);
                return sign ? "-" + answeString : answeString;
            case 1:
                answerLength = binLenToDecLen(extLength);
                answeString = String.format("%" + answerLength + "d", tempnum);
                return sign ? "-" + answeString : answeString;
            case 2:
                answeString = String.format("%" + extLength + "s", 
                    Integer.toBinaryString(tempnum));
                return sign ? "-" + answeString : answeString;
            default:
                throw new Exception(mode + "is wrong");
        }
    }
    
    public static String sigExtStr(String numString,int oriLength,int extLength)throws Exception {
        return sigExtStr(numString, oriLength, extLength,(byte)0);
    }
}